package com.google.code.mongoDao;

import org.apache.log4j.Logger;
import org.bson.types.ObjectId;

import com.google.inject.Inject;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.WriteResult;

/**
 * DAO class for storing DBObjects
 * 
 * @author MWaschkowski
 *
 */
public class DBObjectDao {
  
  private static final String ADMIN = "admin";

  /**
   * ID used by mongo
   */
  public static final String _ID    = "_id";
  
  private Logger             logger = Logger.getLogger(DBObjectDao.class);
  
  private DB                 db;
  
  private DBCommandRunner    dbCommandRunner;

  private Mongo mongo;
  
  @Inject
  public DBObjectDao(DB db, DBCommandRunner commandRunner, Mongo mongo) {
    this.db = db;
    this.dbCommandRunner = commandRunner;
    this.mongo = mongo;
    
  }
  
  /**
   * Saves or updates a record in the specified collection
   * 
   * @param collectionName
   * @param record
   * @return
   */
  public WriteResult save(final String collectionName, final DBObject record) {
    return (WriteResult) dbCommandRunner.runDatabaseCommand(db, new Command() {
      @Override
      public Object execute() {
        logger.debug("Saving " + record + " to:" + collectionName);
        BasicDBObject finder = new BasicDBObject();
        finder.put(_ID, record.get(_ID));
        
        // an update will not updated the uuid, so we have to do so manually
        WriteResult writeResult = db.getCollection(collectionName).update(finder, record, true, false);
        if (writeResult.getField("upserted") != null) {
          logger.debug("adding in _id set during upsert");
          record.put(_ID, writeResult.getField("upserted"));
        }
        logger.debug("Saved " + record + " to:" + collectionName);
        return writeResult;
      }
    });
  }
  
  /**
   * Deletes the specified object from the collection
   * 
   * @param collectionName
   * @param dbObject
   * @return
   */
  public WriteResult delete(final String collectionName, final DBObject dbObject) {
    return (WriteResult) dbCommandRunner.runDatabaseCommand(db, new Command() {
      @Override
      public Object execute() {
        logger.debug("Deleting " + dbObject + " from: " + collectionName);
        WriteResult writeResult = db.getCollection(collectionName).remove(dbObject);
        logger.debug("Deleted " + dbObject + " from: " + collectionName);
        if (logger.isDebugEnabled()) {
          if (writeResult.getError() != null) {
            logger.debug("Error deleting: " + dbObject + " from: " + collectionName + ". Error:" + writeResult.getError());
          }
        }
        return writeResult;
      }
    });
  }
  
  /**
   * Finds a single object from the database
   * 
   * @param collectionName
   * @param findObject
   * @return
   */
  public DBObject findOne(final String collectionName, final DBObject findObject) {
    Command findCommand = new Command() {
      @Override
      public Object execute() {
        BasicDBObject dbObject = (BasicDBObject) db.getCollection(collectionName).findOne(findObject);
        return dbObject;
      }
    };
    return (BasicDBObject) dbCommandRunner.runDatabaseCommand(db, findCommand);
  }
  
  /**
   * Finds records without a search object or field filter.
   * 
   * @param collectionName
   * @param startPosition
   * @param resultSize
   * @return
   */
  public DBCursor find(String collectionName, int startPosition, int resultSize) {
    return find(collectionName, startPosition, resultSize, new BasicDBObject(), null);
  }
  
  /**
   * Finds records without a field filter
   * 
   * @param collectionName
   * @param startPosition
   * @param resultSize
   * @param searchObject
   * @return
   */
  public DBCursor find(String collectionName, int startPosition, int resultSize, DBObject searchObject) {
    return find(collectionName, startPosition, resultSize, searchObject, null);
  }
  
  /**
   * Finds a given number of records in a collection from the specified start position based on the dynaData's properties, and
   * returns fields specified in the fieldObject
   * 
   * @param collectionName
   * @param startPosition
   * @param resultSize
   * @param searchObject
   * @param fieldObject
   * @return
   */
  public DBCursor find(final String collectionName, int startPosition, int resultSize, DBObject searchObject, DBObject fieldObject) {
    DBCursor cursor = null;
    // if no limit, don't limit
    if (resultSize == -1) {
      cursor = db.getCollection(collectionName).find(searchObject, fieldObject).sort(new BasicDBObject("$natural", "-1}")).skip(
          startPosition);
    } else {
      cursor = db.getCollection(collectionName).find(searchObject, fieldObject).sort(new BasicDBObject("$natural", "-1}")).skip(
          startPosition).limit(resultSize);
    }
    return cursor;
  }
  
  public DBObject get(final String recordCollectionName, ObjectId id){
    final DBObject searchObject = new BasicDBObject("_id", id);
    Command findCommand = new Command() {
      @Override
      public Object execute() {
        BasicDBObject dbObject = (BasicDBObject) db.getCollection(recordCollectionName).findOne(searchObject);
        return dbObject;
      }
    };
    return (BasicDBObject) dbCommandRunner.runDatabaseCommand(db, findCommand);
  }
  
  public WriteResult update(String collectionName, DBObject searchRecord, DBObject updater){
    return db.getCollection(collectionName).update(searchRecord, updater, false, true);
  }
  
  public void shutdownDatabase(){
    final DBObject shutdownObject = new BasicDBObject("shutdown", 1);
    Command findCommand = new Command() {
      @Override
      public Object execute() {
        DB adminDatbase = mongo.getDB(ADMIN);
        adminDatbase.command(shutdownObject);
        return null;
      }
    };
    try{
      logger.error("Shutting down mongo...");
      dbCommandRunner.runDatabaseCommand(db, findCommand, false);  
    }catch(Throwable t){
      //catch exception because mongo is down now, and do nothing...
    }
  }
  
  
}
